Skip to main content

FrontProxy

Wolfram Kernel
Execution environment
Notebook`Editor`Kernel`FrontProxy`
Context

A OOP-like abstraction for managing and manipulating graphical or any dynamic frontend instances efficiently and process them in batch. The idea is to use proxy objects as lightweight references to groups of instances (like rectangles, circles combined with other primitives) in a larger scene.

All properties of proxies are stored in linear auto-sizable buffers, which allows JIT-enabled processing and easy sync with a frontend (single transaction).

Constructor

Creates a new proxy type

FrontProxy[{properties__}, body_] _FrontProxyFunction

or if you want to specify mutable properties explicitly

FrontProxy[{properties__}, body_, {mutable__}] _FrontProxyFunction

For example to make proxy for many Disks with controllable opacity, color and position

disk = FrontProxy[{pos, c}, {
Opacity[c], RGBColor[With[{h=c}, {h, 1-h, 0.}]],
Disk[pos, 0.1]
}];

Instancing

After creating a FrontProxyFunction you can start to create your proxies. There are two ways on how to do that

As an object

This is more user-friendly approach, you can directly use FrontProxyFunction and provide arguments as down-values

_FrontProxyFunction [args__] _FrontProxyObject

Each argument from args will be assigned to the defined properties__ in the same order as they appears in the constructor.

For example

disks = Table[disk[RandomReal[{-1,1}, 2], 0.2], {10}]

this line will create 10 random disks proxies.

Methods

With those object you can do following things

Show

Reveals the structure of the instance behind the proxy

Show[__FrontProxyObject] _List

It is usually used to dynamically add new objects on the graphical scene using FrontSubmit or statically place inside Graphics or Graphics3D.

note

It wraps the whole body into Offload to force the execution on the frontned

For example using predefined disk structure

Show @@ disks
output
{FrontInstanceGroup[7808824509103943983,Offload[{Opacity[c$268241[[1]]],RGBColor[{c$268241[[1]],1-c$268241[[1]],0.`}]}]],FrontInstanceGroup[5182874663715795086,Offload[{Opacity[c$268241[[2]]],RGBColor[{c$268241[[2]],1-c$268241[[2]],0.`}]}]],
...

One can add them to the graphics scene basically by placing them directly there

Graphics[{
Show @@ disks
}, PlotRange->{{-1,1}, {-1,1}}]

or dynamically using FrontSubmit and FrontInstanceReference

scene = FrontInstanceReference[];
Graphics[{scene}, PlotRange->{{-1,1}, {-1,1}}]
FrontSubmit[Show @@ disks, scene];
Delete

Dynamically removes an FrontProxyObject

Delete[__FrontProxyObject]

For example if you placed some disks early on the scene, you can remove by directly calling at any place

Delete @@ disks
FrontProxyGet

Gets all properties as a list of the individual instance

FrontProxyGet[_FrontProxyObject] _List

The order is the same as presented in constructor.

FrontProxySet

Sets properties of the individual instance

FrontProxySet[_FrontProxyObject, values_List]

As a batch

A handy object-like representation might be slow, when it comes to creating many instances or removing them. To construct multiple instances use

FrontProxyAdd[_FrontProxyFunction, args__List] _List

as a result it returns a plain List of integers corresponding to the internal Ids

Methods

The following methods can be applied

FrontProxyObject

Converts a pair of the internal Id and FrontProxyFunction to FrontProxyObject to be used in As object

FrontProxyObject[_FrontProxyFunction, Id_Integer] _FrontProxyObject
FrontProxyShow

Reveals the structure of the instance behind the proxy

FrontProxyShow[_FrontProxyFunction, Id_Integer] _

or for a list of proxies ids

FrontProxyShow[_FrontProxyFunction, {Id__Integer}] _
FrontProxyRemove

Removes the given proxy or list of proxies by provided Ids

FrontProxyRemove[_FrontProxyFunction, Id_Integer]
FrontProxyRemove[_FrontProxyFunction, {Id__Integer}]

Common methods

There is a few general methods for _FrontProxyFunction

FrontProxyDispatch

Dispatches the changes made to the properties of proxies

FrontProxyDispatch[_FrontProxyFunction]

It is called usually when all calculations have been finished and an update is needed to see the changes. Behind the scenes it submits all buffers storing the properties of object to the frontend.

For example, after we applied changes to our proxies properties we can dispatch them to the frontend to see changes immediately

Do[
Module[{pos, c},
{pos, c} = FrontProxyGet[i];
FrontProxySet[i, {0.9 pos, c + 0.1}];
]
, {i, disks}];

FrontProxyDispatch[disk];
tip

For the performance reasons, we recommend to avoid atomic operations like FrontProxySet/Get and rely on buffers (see below)

FrontProxyBuffer

Provides a read access to the slice of a given property of all proxies in a form if linear packed array

FrontProxyBuffer[_FrontProxyFunction, index_Integer] _List

where index goes from the first property provided in Constructor to the last. There is a special case

FrontProxyBuffer[_FrontProxyFunction, -1] _List

which returns a boolean array standing for the validity of the property at the given position. Since proxies are dynamic and can be created or removed any time it might temporary lead to "holes" in buffers marked as False in the list.

Each position in buffers does correspond to Id used by proxy in As a batch methods.

FrontProxyBufferSet

Updates a given property buffer with a new array

FrontProxyBuffer[_FrontProxyFunction, index_Integer, new_List]
note

It does not have an effect of FrontProxyDispatch. You will need to call it separately after you have finished all changes with buffers.

For example one can update disks primitives in the following way

  With[{
position = FrontProxyBuffer[disk, 1]
},
With[{velocity = Map[Total] @ Table[potential[a,b], {a, position}, {b, position}]},

FrontProxyBufferSet[disk, 1, position - 0.001 velocity];
FrontProxyDispatch[disk];
];
];

Tips

tip

If you dynamically add proxies to the scene. Call FrontProxyDispatch before submitting it to a scene. This will make sure, that the buffer size is up-to date on the frontend as well.

tip

For fast animations with many proxies involved turn off transition interpolation globally on Graphics using an option TransitionType set to None.

tip

For processing many proxies use batch approach and pure functions with Map, Table or MapThread and etc. Multiple passes using less complicated function cost less, than a single pass with one complex.

tip

If you animate multiple properties of the same primitive to avoid unnecessary update calls on the frontend use Offload with "Static" option, i.e.

FrontProxy[{pos, radius}, Disk[pos, Offload[radius, "Static"->True]]]

uses 1 repaint cycles, while

FrontProxy[{pos, radius}, Disk[pos, radius]]

uses 2 👎

Examples

Spherical attracting molecules

Here we will use Lennard-Jones potential to model a bunch of sphere-like molecules on 2D canvas aka Graphics

Fireworks

A crash test for the frontend system

(* Define the rectangle proxy with initial properties *)
rectangleProxy = FrontProxy[
{position, velocity, rotationAngle, lifeSpan},
Translate[
{Opacity[lifeSpan], RGBColor[With[{l = lifeSpan}, {l, 0, 1 - l}]], Rectangle[{-1, -1}, {1, 1}]},
position
]
];

(* Initialize variables *)
newProxies = {};
expiredProxies = {};
frameCounter = 1;
frameRate = 1;
lastUpdateTime = AbsoluteTime[];

sceneReference = FrontInstanceReference[];

(* Function to add new proxies at a given position *)
addProxyAtPosition[position_] := newProxies = {
newProxies,
FrontProxyAdd[
rectangleProxy,
Sequence @@ Table[
{position, RandomReal[{0.2, 1.8}] {Cos[angle], Sin[angle]} // N, RandomReal[{0, 3.14}], 1.0},
{angle, 0., 2 Pi, 2 Pi / 12.0}
]
]
};

(* Frame update logic *)
Module[{},
EventHandler["frame", Function[Null,
With[{
positions = FrontProxyBuffer[rectangleProxy, 1],
velocities = FrontProxyBuffer[rectangleProxy, 2],
lifeSpans = FrontProxyBuffer[rectangleProxy, 4],
isValid = FrontProxyBuffer[rectangleProxy, -1]
},
(* Identify expired proxies for disposal *)
expiredProxies = MapThread[
If[#1 && #2 < 0.2, #3, Nothing] &,
{isValid, lifeSpans, Range[Length[lifeSpans]]}
];

(* Update positions and life spans *)
FrontProxyBufferSet[rectangleProxy, 1, positions + velocities];
FrontProxyBufferSet[rectangleProxy, 4, lifeSpans * 0.95];
];

(* Dispatch updates to proxies *)
FrontProxyDispatch[rectangleProxy];

(* Remove expired proxies *)
If[Length[expiredProxies] > 0,
FrontProxyRemove[rectangleProxy, expiredProxies];
expiredProxies = {};
];

(* Submit new proxies *)
If[Length[newProxies] > 0,
FrontSubmit[FrontProxyShow[rectangleProxy, newProxies // Flatten], sceneReference];
newProxies = {};
];

(* Update FPS counter *)
With[{currentTime = AbsoluteTime[]},
If[currentTime - lastUpdateTime > 1.0,
frameRate = Round[(frameCounter + frameRate) / 2.0];
frameCounter = 1;
lastUpdateTime = currentTime;
,
frameCounter++;
];
];
]]
];

(* Create the graphics and event handlers *)
Graphics[
{
sceneReference,
{Directive[FontSize -> 20], Text[frameRate // Offload, {-80, -80}]},
AnimationFrameListener[frameCounter // Offload, "Event" -> "frame"],
EventHandler[
Graphics`Canvas[],
{"mousemove" -> addProxyAtPosition}
]
},
PlotRange -> {{-100, 100}, {-100, 100}},
TransitionType -> None
]